package com.xiaoyang.system.service.exam.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.pagehelper.PageHelper;
import com.xiaoyang.common.core.constants.Constants;
import com.xiaoyang.common.core.enums.ResultCode;
import com.xiaoyang.common.security.exception.ServiceException;
import com.xiaoyang.system.domain.exam.Exam;
import com.xiaoyang.system.domain.exam.ExamQuestion;
import com.xiaoyang.system.domain.exam.dto.ExamAddDTO;
import com.xiaoyang.system.domain.exam.dto.ExamEditDTO;
import com.xiaoyang.system.domain.exam.dto.ExamQueryAddDTO;
import com.xiaoyang.system.domain.exam.dto.ExamQueryDTO;
import com.xiaoyang.system.domain.exam.vo.ExamDetailVO;
import com.xiaoyang.system.domain.exam.vo.ExamVO;
import com.xiaoyang.system.domain.question.Question;
import com.xiaoyang.system.domain.question.vo.QuestionVO;
import com.xiaoyang.system.manager.ExamCacheManager;
import com.xiaoyang.system.mapper.exam.ExamMapper;
import com.xiaoyang.system.mapper.exam.ExamQuestionMapper;
import com.xiaoyang.system.mapper.question.QuestionMapper;
import com.xiaoyang.system.service.exam.IExamService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

@Slf4j
@Service
public class ExamServiceImpl extends ServiceImpl<ExamQuestionMapper, ExamQuestion> implements IExamService {

    @Autowired
    private ExamMapper examMapper;

    @Autowired
    private QuestionMapper questionMapper;

    @Autowired
    private ExamQuestionMapper examQuestionMapper;

    @Autowired
    private ExamCacheManager examCacheManager;

    /**
     * 竞赛列表
     * @param examQueryDTO
     * @return
     */
    @Override
    public List<ExamVO> list(ExamQueryDTO examQueryDTO) {
        log.info("查询竞赛列表");
        // 分页查询
        PageHelper.startPage(examQueryDTO.getPageNum(), examQueryDTO.getPageSize());
        return examMapper.selectExamList(examQueryDTO);
    }

    /**
     * 添加竞赛
     * @param examAddDTO
     * @return
     */
    @Override
    public String add(ExamAddDTO examAddDTO) {
        log.info("添加竞赛");

        // 检验竞赛时间
        checkExamSaveParams(examAddDTO, null);

        // DTO 转实体
        Exam exam = new Exam();
        BeanUtil.copyProperties(examAddDTO, exam);

        // 存储数据库 并返回竞赛id
        examMapper.insert(exam);
        return exam.getExamId().toString();
    }


    /**
     * 添加竞赛题目
     * @param examQueryAddDTO
     * @return
     */
    @Override
    public boolean questionAdd(ExamQueryAddDTO examQueryAddDTO) {
        log.info("添加竞赛题目");

        // 判断竞赛id是否存在
        Exam exam = getExam(examQueryAddDTO.getExamId());

        // 竞赛已经开始 无法编辑
        extracted(exam);

        if (Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }

        // 判断题目id是否存在
        Set<Long> questionIdSet = examQueryAddDTO.getQuestionIdSet();
        if (CollectionUtil.isEmpty(questionIdSet)) {
            return true;
        }

        List<Question> questions = questionMapper.selectBatchIds(questionIdSet);    // 批量查询
        if (CollectionUtil.isEmpty(questions) || questions.size() != questionIdSet.size()) {
            throw new ServiceException(ResultCode.EXAM_QUESTION_NOT_EXISTS);
        }

        // 批量新增竞赛题目
        return saveExamQuestion(exam, questionIdSet);
    }

    /**
     * 删除竞赛题目
     * @param examId        竞赛id
     * @param questionId    题目id
     * @return
     */
    @Override
    public int questionDelete(Long examId, Long questionId) {
        log.info("删除竞赛题目");

        Exam exam = getExam(examId);

        // 竞赛已经开始 无法编辑
        extracted(exam);

        if (Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }

        int result = examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId)
                .eq(ExamQuestion::getQuestionId, questionId));
        return result;
    }

    /**
     * 竞赛详情
     * 步骤: 查询竞赛基本信息 + 查询竞赛题目信息
     * @param examId
     */
    @Override
    public ExamDetailVO detail(Long examId) {
        log.info("查询竞赛详情");
        ExamDetailVO examDetailVO = new ExamDetailVO();
        // 根据竞赛id查询竞赛信息
        Exam exam = getExam(examId);

        // 实体转VO
        BeanUtil.copyProperties(exam, examDetailVO);

        // 根据竞赛id查询竞赛题目信息
        List<ExamQuestion> examQuestionList = examQuestionMapper.selectList(new LambdaQueryWrapper<ExamQuestion>()
                .select(ExamQuestion::getQuestionId)    // 查询题目id
                .eq(ExamQuestion::getExamId, examId)
                .orderByAsc(ExamQuestion::getQuestionOrder));// 排序

        if (CollectionUtil.isEmpty(examQuestionList)) {
            // 竞赛题目为空 返回VO -- 只能竞赛信息
            return examDetailVO;
        }

        // 获取题目id集合
        List<Long> qustionIdList = examQuestionList.stream()
                .map(ExamQuestion::getQuestionId).toList();

        // 根据题目id集合查询题目信息 -- select * from tb_question where question_id in (1,2,3)
        List<Question> questionList = questionMapper.selectList(new LambdaQueryWrapper<Question>()
                .select(Question::getQuestionId, Question::getTitle, Question::getDifficulty)
                .in(Question::getQuestionId, qustionIdList));

        // 实体集合转VO集合
        List<QuestionVO> questionVOList = BeanUtil.copyToList(questionList, QuestionVO.class);

        // 设置题目列表值
        examDetailVO.setExamQuestionList(questionVOList);

        return examDetailVO;
    }

    /**
     * 编辑竞赛
     * @param examEditDTO
     */
    @Override
    public int edit(ExamEditDTO examEditDTO) {
        log.info("编辑竞赛");
        // 查询竞赛id
        Exam exam = getExam(examEditDTO.getExamId());
        if (Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }
        extracted(exam);

        // 校验参数
        checkExamSaveParams(examEditDTO, examEditDTO.getExamId());

        // 实体赋值
        exam.setTitle(examEditDTO.getTitle());
        exam.setStartTime(examEditDTO.getStartTime());
        exam.setEndTime(examEditDTO.getEndTime());

        // 更新数据库
        return examMapper.updateById(exam);
    }

    /**
     * 删除竞赛
     * @param examId 竞赛id
     * @return
     */
    @Override
    public int delete(Long examId) {
        log.info("删除竞赛");
        // 根据id查询竞赛
        Exam exam = getExam(examId);
        if (Constants.TRUE.equals(exam.getStatus())) {
            throw new ServiceException(ResultCode.EXAM_IS_PUBLISH);
        }
        // 根据id查询是否开赛
        extracted(exam);

        // 删除竞赛题目
        examQuestionMapper.delete(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        // 删除竞赛基本信息
        return examMapper.deleteById(examId);
    }

    /**
     * 发布竞赛
     * @param examId 竞赛id
     * @return
     */
    @Override
    public int publish(Long examId) {
        log.info("发布竞赛");

        // 查询竞赛是否存在
        Exam exam = getExam(examId);

        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_FINISH);
        }

        // 查询竞赛竞赛题目是否存在
        Long nums = examQuestionMapper.selectCount(new LambdaQueryWrapper<ExamQuestion>()
                .eq(ExamQuestion::getExamId, examId));
        if (nums == null || nums <= 0) {
            throw new ServiceException(ResultCode.EXAM_NOT_HAS_QUESTION);
        }

        // 设置发布状态
        exam.setStatus(Constants.TRUE);

        // 将发布的竞赛存储在redis中
        examCacheManager.addCache(exam);

        // 存储数据库
        return examMapper.updateById(exam);
    }

    /**
     * 取消发布竞赛
     * 实现步骤 : 查询竞赛是否存在 + 竞赛是否开赛 + 设置发布状态 + 存储数据库
     * @param examId 竞赛id
     * @return
     */
    @Override
    public int cancelPublish(Long examId) {
        log.info("取消发布竞赛");

        // 查询竞赛是否存在
        Exam exam = getExam(examId);

        if (exam.getEndTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_IS_FINISH);
        }

        // 查询竞赛是否开赛
        extracted(exam);

        // 设置发布状态
        exam.setStatus(Constants.FALSE);

        // 删除redis中的数据
        examCacheManager.deleteCache(examId);

        // 存储数据库
        return examMapper.updateById(exam);
    }

    /**
     * 校验竞赛是否开始
     * @param exam 竞赛实体
     */
    private static void extracted(Exam exam) {
        // 竞赛已经开始 无法编辑竞赛
        if (exam.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_STARTED);
        }
    }

    /**
     * 校验竞赛标题 开始时间 结束时间
     * @param examSaveDTO
     */
    private void checkExamSaveParams(ExamAddDTO examSaveDTO, Long examId) {
        // 校验标题是否存在
        List<Exam> exams = examMapper
                .selectList(new LambdaQueryWrapper<Exam>()
                        .eq(Exam::getTitle, examSaveDTO.getTitle())
                        .ne(examId != null, Exam::getExamId, examId));

        if (CollectionUtil.isNotEmpty(exams)) {
            throw new ServiceException(ResultCode.FAILED_ALREADY_EXISTS);
        }

        // 校验时间--开始时间不能小于当前时间--结束时间不能小于开始时间
        if (examSaveDTO.getStartTime().isBefore(LocalDateTime.now())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_BEFORE_CURRENT_TIME);
        }
        if (examSaveDTO.getStartTime().isAfter(examSaveDTO.getEndTime())) {
            throw new ServiceException(ResultCode.EXAM_START_TIME_AFTER_END_TIME);
        }
    }

    /**
     * 批量新增竞赛题目
     * @param exam
     * @param questionIdSet
     */
    private boolean saveExamQuestion(Exam exam, Set<Long> questionIdSet) {
        int num = 1;
        List<ExamQuestion> examQuestionList = new ArrayList<>();
        for (Long questionId : questionIdSet) {
            // 设置值
            ExamQuestion examQuestion = new ExamQuestion();
            examQuestion.setExamId(exam.getExamId());
            examQuestion.setQuestionId(questionId);
            examQuestion.setQuestionOrder(num++);
            examQuestionList.add(examQuestion);
        }
        return saveBatch(examQuestionList); // 批量新增数据
        // * mybatis-plus扩展包 批量新增数据
    }

    /**
     * 判断竞赛id是否存在
     * @param examId
     * @return
     */
    private Exam getExam(Long examId) {
        Exam exam = examMapper.selectById(examId);
        if (exam == null) {
            throw new ServiceException(ResultCode.EXAM_NOT_EXISTS);
        }
        return exam;
    }
}
